# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Name:        net_io.py
# Purpose:     GAEDrive Client: network interface
#
# Author:      Crane Chu <cranechu (at) gmail.org>
#
# Created:     Nov. 20, 2009
# Copyright:   Copyright 2009,2010 Crane Chu
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#-------------------------------------------------------------------------------
#!/usr/bin/env python

__author__ = 'Crane Chu'


import os
import sys
import json
import urllib
import urllib2
import logging
import getpass
from gaedrive_config import MASTER_SERVER_URL, CHUNKURLHEAD
from appengine_rpc import HttpRpcServer, GetPlatformToken

class NetIO(object):
    def __init__(self):
        """ Returns an instance of an AbstractRpcServer.
        
        Returns:
          A new AbstractRpcServer, on which RPC calls can be made.
          use its Send() method
        """
        def _GetUserAgent(get_platform = GetPlatformToken):
            """Determines the value of the 'User-agent' header to use for 
            HTTP requests.
            
            If the 'APPCFG_SDK_NAME' environment variable is present, that 
            will be used as the first product token in the user-agent.
            
            Args:
              get_platform: Used for testing.
            
            Returns:
              String containing the 'user-agent' header value, which includes 
              the SDK version, the platform information, and the version of 
              Python; e.g., 'appcfg_py/1.0.1 Darwin/9.2.0 Python/2.5.2'.
            """
            product_tokens = []
            
            sdk_name = os.environ.get('APPCFG_SDK_NAME')
            if sdk_name:
                product_tokens.append(sdk_name)
            else:
                product_tokens.append('appcfg_py/unknown')
            
            product_tokens.append(get_platform())
            
            python_version = '.'.join(str(i) for i in sys.version_info)
            product_tokens.append('Python/%s' % python_version)
            
            return ' '.join(product_tokens)
        
        def _GetUserCredentials():
            """Prompts the user for a username and password."""
            email = raw_input('Gmail Account: ')
            if not email:
                email = None
            elif '@gmail.com' not in email:
                if '@' in email:
                    # other type account
                    email = None
                else:
                    email += '@gmail.com'
            elif '@gmail.com' in email:
                email = email
            else:
                email = None
            
            if email is not None:
                password = getpass.getpass('Password for %s: ' % email)
            else:
                print("Wrong account. Please log in with your Gmail Account.")
                password = None
                
            return (email, password)

        if 'localhost' in MASTER_SERVER_URL:
            auth_function = lambda: ('test@example.com', 'password')
        else:
            auth_function = _GetUserCredentials
            
        self.master = HttpRpcServer(MASTER_SERVER_URL,
                                    auth_function,
                                    _GetUserAgent(),
                                    'Google-appcfg-unknown',
                                    save_cookies = True,
                                    auth_tries = 2,
                                    secure = False)
        if self.master:
            self.master.authenticated = True
            self.offline = False
        else:
            # offline mode
            self.offline = True
            
    def rpc_master(self, funcname, *args):
        if self.offline:    return False
    
        logging.debug(">>>>> master: %s, %s" % (funcname, repr(args)[:50]))
        
        # try 3 times
        for i in range(3):
            request_path = "/file_op"
            payload = urllib.urlencode({"args" : json.dumps(args)})
            content_type = "application/x-www-form-urlencoded"
            try:
                resp = self.master.Send(request_path, payload, content_type,
                                        func = funcname)
                ret = json.loads(resp.decode('utf-8'))
                break
            except:
                ret = None
                logging.debug("failed in i=%d" % i)
                continue

        logging.debug("<<<<< return: %s, %s" % (funcname, repr(ret)[:50]))
    
        return ret
    
    def readChunk(self, chunkserver_url, chunk_key, rand):
        if self.offline:    return False
          
        logging.debug(">>>>> chunk read: %s:%s" % (chunkserver_url, chunk_key))
        
        # try 3 times
        for i in range(3):
            url = CHUNKURLHEAD + chunkserver_url + '/read_chunk'
            payload = urllib.urlencode({'key' : chunk_key,
                                        'rand' : rand})
            req = urllib2.Request(url, payload)
            
            res = urllib2.urlopen(req)
            if res.code == 200:
                ret = str(res.read())
                break
            else:
                ret = False
                logging.debug("failed in i=%d" % i)
                continue

        logging.debug("<<<<< chunk read: %s" % ret[:50])
        
        return ret

    def writeChunk(self, chunkserver_url, chunk_key, chunk_data, rand):
        """
        Post files to an http host as multipart/form-data.
        files is a sequence of (name, filename, value) elements for data to be 
            uploaded as files
        Return the server's response page.
        """
        if self.offline:    return False

        logging.debug(">>>>> chunk write: %s:%s" % (chunkserver_url, chunk_key))
        
        # try 3 times
        for i in range(3):
            # data
            boundary = '----------GAEDrive_CHUNK_DATA_$'
            header1 = 'Content-Disposition: ' + \
                                'form-data; name="data"; filename="_"'
            header2 = 'Content-Type: application/octet-stream'
            L = []
            L.append('--' + boundary)
            L.append(header1)
            L.append(header2)
            L.append('')
            L.append(chunk_data)
            L.append('--' + boundary + '--')
            L.append('')
            data = '\r\n'.join(L)
            
            # url
            selector = '/write_chunk?key=%s&rand=%d' % (chunk_key, rand)
            url = CHUNKURLHEAD + chunkserver_url + selector
            
            # headers
            headers = {'User-Agent': 'GAEDrive_Crane_Chu',
                       'Content-Type': 'multipart/form-data; ' + \
                                        'boundary=' + boundary}
            
            # url request
            req = urllib2.Request(url, data, headers)
            
            # get response and return value
            try:
                res = urllib2.urlopen(req)
            except:
                ret = False
                logging.debug("failed in i=%d" % i)
                continue
            
            if res.code == 200:
                ret = int(res.read())
                break
            else:
                ret = False
                logging.debug("failed in i=%d" % i)
                continue
    
        logging.debug("<<<<< chunk write: %d - %d" % (len(chunk_data), ret))
        
        return ret
